//****************************************************************************
// chainparser.cpp
// implamentaion of the ChainParser class.
//
// Author: Einat Zuker
//****************************************************************************
#include "chainparser.h"

//----------------------------------------------------------------------------
// Ctor
// constructs a ChainParser object performs an initial parsing of the file
// and initiates the member fields of the ChainParser.
//
// @param fileName
//		the name of the chain file to parse.
//
// @throw ChainParserExepetion
//		if the parsing failed for some reason.
//----------------------------------------------------------------------------
ChainParser::ChainParser(string fileName)
{
	m_File.open(fileName.c_str(), std::ios::in);
	if(m_File.fail())
	{
		throw (new ChainParserExepetion("failed to open the chain file: " + fileName));
	}



	//backword compatibility - skip first row of the file if needed
	char firstChar[2] = {0};
	firstChar[0]=m_File.peek();
	if( firstChar[0] != '0' && atoi(firstChar) == 0 ) //first char is not a number
		m_File.ignore(INT_MAX, '\n');

	char buffer[LARGE_BUFFER_SIZE];
	
	//read the length of the binary word
	m_File.getline(buffer, LARGE_BUFFER_SIZE, ' ');
	m_BinaryWordLength = atoi(buffer);
	if (m_BinaryWordLength < 1 || m_BinaryWordLength > MAX_BINARY_WORD_LENGTH)
		throw (new ChainParserExepetion("ilegal binary word length: " + m_BinaryWordLength));
	
	memset(buffer, 0, LARGE_BUFFER_SIZE);
	
	//read the wghit of the binaray word
	m_File.getline(buffer, LARGE_BUFFER_SIZE, ' ');
	m_BinaryWordWeight = atoi(buffer);
	if(m_BinaryWordWeight < 0 || m_BinaryWordWeight >= m_BinaryWordLength)
		throw (new ChainParserExepetion("ilegal binary word Weight: " + m_BinaryWordWeight));
	
	memset(buffer, 0, LARGE_BUFFER_SIZE);

	//read the number of groups in the file
	m_File.getline(buffer, LARGE_BUFFER_SIZE, '\n');
	m_NumberOfGroups = atoi(buffer);
	if(m_NumberOfGroups < 0)
		throw (new ChainParserExepetion("ilegal number of groups: " + m_NumberOfGroups));
	
	memset(buffer, 0, LARGE_BUFFER_SIZE);

	//read the group summery
	if(m_NumberOfGroups > 0)
		{
		m_GroupSummary = new int[m_NumberOfGroups];
		m_File.getline(buffer, LARGE_BUFFER_SIZE, ':'); //skip the title
		int pos = m_File.tellg();
		m_File.seekg(pos + 1);	//skip the first space after title
		for(int i=0; i < m_NumberOfGroups; i++)//note! after last group size must be a space
		{
			m_File.getline(buffer, LARGE_BUFFER_SIZE, ' ');
			//check that there were'nt too little groups in the file
			string readResult = buffer;
			if( readResult.find("#") != string::npos )
				throw (new ChainParserExepetion("not enough groups in group summery. expected number of groups is: " + m_NumberOfGroups));
			m_GroupSummary[i] = atoi(buffer);
			if(m_GroupSummary[i] < 1)
				throw (new ChainParserExepetion("ilegal group size: " + m_GroupSummary[i]));
		}
		
		//chek there arm't too many groups in the file
		//skip the first charecter after the summery (this charcter shuld be a '\n')
		m_File.ignore(1, ' ');
		//backword compatibility - chek if the groups start with no line breack
		char nextChar = m_File.peek();	
		if( nextChar != '#')
		{
			m_File.ignore(1, ' '); 
			nextChar = m_File.peek();
			if(nextChar != '#')
				throw (new ChainParserExepetion("too many groups in group summery. expected number of groups is: " + m_NumberOfGroups));
		}
		
		m_FirstGroupPos = m_File.tellg(); //point to the name of the first group
	}
	else //m_NumberOfGroups == 0
	{
		m_FirstGroupPos = 0;
		m_GroupSummary = NULL;
	}
}

//----------------------------------------------------------------------------
// Dtor
// destroys a ChainParser object and frees all memory that was allocated
// during its construction.
//----------------------------------------------------------------------------
ChainParser::~ChainParser()
{
	delete m_GroupSummary;
	m_File.close();
}


//----------------------------------------------------------------------------
// GetBinaryWordLength
// returns the binary word Length of the chain file
//
// @return int
//		the binary word Length of the chain file.
//----------------------------------------------------------------------------
int ChainParser::GetBinaryWordLength()
{
	return m_BinaryWordLength;
}

//----------------------------------------------------------------------------
// GetBinaryWordWeight
// returns the binary word weight of the chain file
//
// @return int
//		the binary word weight of the chain file.
//----------------------------------------------------------------------------
int ChainParser::GetBinaryWordWeight()
{
	return m_BinaryWordWeight;
}

//----------------------------------------------------------------------------
// GetNumberOfGroups
// returns the number of groups in the chain file
//
// @return int
//		the number of groups in the chain file.
//----------------------------------------------------------------------------
int ChainParser::GetNumberOfGroups()
{
	return m_NumberOfGroups;
}

//----------------------------------------------------------------------------
// GetGroupSummary
// returns the group summary in the chain file. (an array of all the group sizes) 
//
// @return int*
//		an array of all the group sizes 
//		note: the size of the array is given by calling GetNumberOfGroups()
//----------------------------------------------------------------------------
int* ChainParser::GetGroupSummary()
{
	return m_GroupSummary;
}

//----------------------------------------------------------------------------
// GetFirstVertex
// returns the first vertex in the given group.
// this function allocates memory for the returned value and it is 
// the responsibility of the caller to free this memory.
//
// @param groupId
//		the group id to get its first vertex from.
//
// @return vector<bool>*
//		a pointer to the vertex name
//
// @thorw ChainParserExepetion
//		in case an error occurred
//----------------------------------------------------------------------------
vector<bool>* ChainParser::GetFirstVertex(int groupId)
{
	if (groupId > m_NumberOfGroups || groupId <= 0)
		throw (new ChainParserExepetion("passed ileagl group index: " + groupId));

	//get the index of the current group the file is poining at
	char buffer[SMALL_BUFFER_SIZE] = {0};
	m_File.ignore(2, ' '); //skip the "# " prefix of the index
	m_File.getline(buffer, SMALL_BUFFER_SIZE, '\n');
	int currGroupIndex = atoi(buffer);
	if(currGroupIndex > m_NumberOfGroups || currGroupIndex <= 0)
		throw (new ChainParserExepetion("file containes ileagl group index: " + groupId));

	for(int i=0; i < m_NumberOfGroups; i++)
	{
		if(currGroupIndex == groupId)
			break;

		m_File.ignore(INT_MAX, '#'); //skip an entire group and reach the next "#" prefix sign
		
		// if serach reachd the end of file, we go back to the first group in the file.
		if(m_File.eof())
		{
			m_File.seekg(0, std::ios::beg);
			m_File.seekg(m_FirstGroupPos);
			m_File.ignore(1, '#'); //skip the "#" prefix sign
		}
		m_File.ignore(1, ' '); //skip the space after the "#"
		m_File.getline(buffer, SMALL_BUFFER_SIZE, '\n');
		currGroupIndex = atoi(buffer);
	}
	
	if(currGroupIndex != groupId)
		throw (new ChainParserExepetion("missing group index from the chain file for group index:" + groupId));


	return ReadAndProcessVertexName();
}

//----------------------------------------------------------------------------
// GetNextVertex
// returns the next vertex of the current group.
// this funcation can only be called after calling GetFirstVertex
// this function allocates memory (indirectly) for the returned value and it is 
// the responsibility of the caller to free this memory.
//
// @return vector<bool>*
//		a pointer to the vertex name.
//		if there are no more names in the group NULL pointer is returned
//----------------------------------------------------------------------------
vector<bool>* ChainParser::GetNextVertex()
{
	return ReadAndProcessVertexName();
}

//****************************************************************************
// private functions
//****************************************************************************

//----------------------------------------------------------------------------
// ReadAndProcessVertexName
// this function reads the current vertex name from the chain file
// and process the result to a vector<bool> object.
// this function allocates memory for the returned value and it is 
// the responsibility of the caller to free this memory.
//
// @return vector<bool>*
//		a pointer to the vertex name.
//		if there are no more names in the group NULL pointer is returned
//----------------------------------------------------------------------------
vector<bool>* ChainParser::ReadAndProcessVertexName()
{
	//the file pointer is located at the begining of the vertex 
	//we just need to return it
	char buffer[SMALL_BUFFER_SIZE];
	m_File.getline(buffer, SMALL_BUFFER_SIZE, '\n');
	
	//if we got an empty string, we dont have any more vertecies in this group.
	if(buffer[0] == '\0') 
	{
		if(m_File.eof())
		{
			m_File.seekg(0, std::ios::beg);
			m_File.seekg(m_FirstGroupPos);
		}

		return NULL;
	}

	//skip leading zeroes
	int leadingZerosCount = strlen(buffer) - m_BinaryWordLength; 
	int firstRealBitIndex = leadingZerosCount; //(leadingZerosCount == 0 ? 0 : leadingZerosCount+1);

	char bitStr[2] = {0};
	bool *bit = new bool[m_BinaryWordLength];
	int j = firstRealBitIndex;
	for(int i=0; i < m_BinaryWordLength; i++, j++)
	{
		bitStr[0] = buffer[j];
		bit[i] = (strcmp(bitStr,"1") == 0 ? true : false);
	}
	vector<bool> *vertexName = new vector<bool>(bit, bit + m_BinaryWordLength);

	delete[] bit;
	return vertexName;
}